/**
 * 
 */
package core.evolutionary;

import java.util.Random;
import java.util.Set;

import core.Problem;

/**
 * @author tptak
 * 
 */
public class Agent {
	// private String name;
	protected Island island;
	protected Problem problem;
	protected double genotype[];
	protected double results[];
	protected int resources[];
	protected double actionsProbability;
	protected int whenToReproduce;
	protected int whenToDie;
	protected int initialLifeEnergyLevel;
	protected int transferableLifeEnergyQuantity;
	protected double mutationParameter;
	// private int whenToBecomeElitist;
	// private int transferableElitarityQuantity;
	// private int meetingsCounter;
	// private int dominatingCounter;
	// private int dominatiionCounter;
	// private double densityVerificationDistance;
	// private int closeMeetings;
	// private int initialElitarityLevel;
	protected Random generator;

	public Agent(Island island, Problem problem, double[] genotype,
			int initialLifeEnergyLevel, int transferableLifeEnergyQuantity,
			int whenToDie, int whenToReproduce, double actionsProbability,
			double mutationParameter, Random generator) {
		this.island = island;
		this.island.add(this);
		this.getEnvironment().add(this);
		this.results = null;
		this.problem = problem;
		this.genotype = genotype;
		this.initialLifeEnergyLevel = initialLifeEnergyLevel;
		this.transferableLifeEnergyQuantity = transferableLifeEnergyQuantity;
		this.whenToDie = whenToDie;
		this.whenToReproduce = whenToReproduce;
		this.actionsProbability = actionsProbability;
		this.mutationParameter = mutationParameter;
		this.generator = generator;
		this.resources = new int[1];
	}

	public void lifeStep() {
		// smierc
		if (this.resources[0] <= this.whenToDie) {
			this.island.giveResources(0, this.resources[0]);
			this.island.remove(this);
			this.getEnvironment().remove(this);
			return;
		}
		// migracja
		Set<Edge> edges;
		int setSize;
		int randomValue;

		if (generator.nextDouble() < this.actionsProbability / 2
				&& (setSize = (edges = this.island.getMigratableEdges()).size()) != 0) {
			randomValue = generator.nextInt(setSize);
			Edge edge = null;
			for (Edge element : edges) {
				if (randomValue-- <= 0) {
					edge = element;
					break;
				}
			}
			int cost;
			if (this.resources[0] > (cost = edge.getCost()) + this.whenToDie) {
				this.island.giveResources(0, this.takeResource(0, cost));
				this.island.remove(this);
				this.island = edge.getToIsland();
				this.island.add(this);
			}
		}

		// spotkanie
		Set<Agent> neighbours;
		setSize = (neighbours = this.island.getNeighbours(this)).size();
//		System.out.println(setSize);

		if (generator.nextDouble() < this.actionsProbability && setSize != 0) {
			randomValue = generator.nextInt(setSize);
			Agent agent = null;
			for (Agent element : neighbours) {
				if (randomValue-- <= 0) {
					agent = element;
					break;
				}
			}
			double hisResults[] = agent.getResults();
			try {
				if (problem.isFirstDominatingTheOther(this.getResults(),
						hisResults)) {
//					System.out.println("a ("+this.results[0]+";"+this.results[1]+"),("+hisResults[0]+";"+hisResults[1]+")");
					agent.transferResourceTo(this, 0,
							transferableLifeEnergyQuantity);
				} else if (problem.isFirstDominatingTheOther(hisResults, this
						.getResults())) {
//					System.out.println("b ("+this.results[0]+";"+this.results[1]+"),("+hisResults[0]+";"+hisResults[1]+")");
					this.transferResourceTo(agent, 0,
							transferableLifeEnergyQuantity);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

		}

		// prokreacja
		if (generator.nextDouble() < this.actionsProbability && (setSize != 0) && this.wantsToReproduce()) {
			randomValue = generator.nextInt(setSize);
			Agent agent = null;
			for (Agent element : neighbours) {
				if (randomValue-- <= 0) {
					agent = element;
					break;
				}
			}

			if (agent.wantsToReproduce()) {
				double[] hisGenotype = agent.getGenotype();
				int length = this.genotype.length;
				double[] genotype1 = new double[length];
				double[] genotype2 = new double[length];
				length--;
				int i = 0;
				double proportion;
				try {
					while (true) {
						proportion = (i + 0.0) / length;
						genotype1[i] = this.genotype[i] * proportion
								+ hisGenotype[i] * (1 - proportion);
						genotype2[i] = hisGenotype[i] * proportion
								+ this.genotype[i] * (1 - proportion);
						i++;
					}
				} catch (ArrayIndexOutOfBoundsException e) {
					// tu nic nie robimy
				}

				this.mutate(genotype1);
				this.mutate(genotype2);
				problem.normalize(genotype1);
				problem.normalize(genotype2);
				Agent a1 = new Agent(
						island,
						problem,
						genotype1,
						(this.initialLifeEnergyLevel + agent
								.getInitialLifeEnergyLevel()) / 2,
						(this.transferableLifeEnergyQuantity + agent
								.getTransferableLifeEnergyQuantity()) / 2,
						(this.whenToDie + agent.getWhenToDie()) / 2,
						(this.whenToReproduce + agent.getWhenToReproduce()) / 2,
						this.actionsProbability, this.mutationParameter, this.generator);
				Agent a2 = new Agent(
						island,
						problem,
						genotype1,
						(this.initialLifeEnergyLevel + agent
								.getInitialLifeEnergyLevel()) / 2,
						(this.transferableLifeEnergyQuantity + agent
								.getTransferableLifeEnergyQuantity()) / 2,
						(this.whenToDie + agent.getWhenToDie()) / 2,
						(this.whenToReproduce + agent.getWhenToReproduce()) / 2,
						this.actionsProbability, this.mutationParameter, this.generator);
//				island.add(a1);
//				island.add(a2);
//				getEnvironment().add(a1);
//				getEnvironment().add(a2);
				this.transferResourceTo(a1, 0, initialLifeEnergyLevel / 2);
				this.transferResourceTo(a2, 0, initialLifeEnergyLevel / 2);
				agent.transferResourceTo(a1, 0, agent.getInitialLifeEnergyLevel() / 2);
				agent.transferResourceTo(a2, 0, agent.getInitialLifeEnergyLevel() / 2);
				a1.lifeStep();
				a2.lifeStep();
			}
		}

	}
	
	public void transferResourceTo(Agent to, int resourceId, int resourceAmount) {
		to.giveResource(resourceId, this.takeResource(resourceId, resourceAmount));
	}

	public int getInitialLifeEnergyLevel() {
		return initialLifeEnergyLevel;
	}

	protected EvolutionaryEnvironment getEnvironment() {
		return this.island.getEnvironment();
	}

	public double[] getGenotype() {
		return genotype;
	}

	public double[] getResults() {
		if (this.results == null) {
			this.results = problem.calculate(this.genotype);
		}
		return this.results;
	}

	public int giveResource(int resourceId, int resourceAmount) {
		this.resources[resourceId] += resourceAmount;
		return resourceAmount;
	}

	public int getTransferableLifeEnergyQuantity() {
		return transferableLifeEnergyQuantity;
	}

	public int getWhenToDie() {
		return whenToDie;
	}

	public int getWhenToReproduce() {
		return whenToReproduce;
	}

	protected void mutate(double[] genotype) {
		double rnd, delta1, delta2, mut_pow, deltaq;
		double y, yl, yu, val, xy;
		int var;
		try {
			var = 0;
			while (true) {
				if (this.generator.nextDouble() < 2 * this.actionsProbability / 3) {
					y = genotype[var];
					yl = problem.getLowerBound(var);
					yu = problem.getUpperBound(var);
					delta1 = (y - yl) / (yu - yl);
					delta2 = (yu - y) / (yu - yl);
					rnd = this.generator.nextDouble();
					mut_pow = 1.0 / (this.mutationParameter + 1.0);
					if (rnd <= 0.5) {
						xy = 1.0 - delta1;
						val = 2.0
								* rnd
								+ (1.0 - 2.0 * rnd)
								* (Math.pow(xy, (this.mutationParameter + 1.0)));
						deltaq = java.lang.Math.pow(val, mut_pow) - 1.0;
					} else {
						xy = 1.0 - delta2;
						val = 2.0
								* (1.0 - rnd)
								+ 2.0
								* (rnd - 0.5)
								* (java.lang.Math.pow(xy,
										(this.mutationParameter + 1.0)));
						deltaq = 1.0 - (java.lang.Math.pow(val, mut_pow));
					}
					y = y + deltaq * (yu - yl);
					if (y < yl)
						y = yl;
					if (y > yu)
						y = yu;
					genotype[var] = y;
				}
				var++;
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			// tu nic nie robimy
		}
	}

	public void resetResults() {
		this.results = null;
	}

	public int takeResource(int resourceId, int resourceAmount) {
		int tmp = (this.resources[resourceId] < resourceAmount) ? this.resources[resourceId]
				: resourceAmount;
		this.resources[resourceId] -= tmp;
		return tmp;
	}

	public boolean wantsToReproduce() {
		return this.resources[0] >= this.whenToReproduce;
	}

	public String toString() {
		return "Agent [" + this.resources[0] + "]"+((this.results==null)?"":"("+this.results[0]+";"+this.results[1]+")");// + this.name;
	}
	
	
}
